Skip to main content

02 声明语句

声明

被定义声明的语法结构都一定是语句,并且都用于声明一个或多个标识符,包括变量、常量等。

let

声明变量,不可在赋值之前读。

const

声明常量,不可写。

var

声明变量,在赋值之前可读取到 undefined 值。

function

声明变量,该变量指向一个函数。

class

声明变量,该变量指向一个类(该类的作用域内部是处理严格模式的)。

import

导入标识符并作为常量(可以有多种声明标识符的模式和方法)。

有潜在的声明标识符的能力,但不是严格意义上的声明语句:

for (var|let|const x)

for 语句有多种语法来声明一个或多个标识符,用作循环变量。

try … catch (x)

catch 子句可以声明一个或多个标识符,用作异常对象变量。

  • JavaScript 可以通过静态语法分析发现那些声明的标识符;
  • 标识符对应的变量 / 常量在用户代码执行前就已经被创建在作用域中。

从读取值到赋值

声明在语法分析阶段处理,因此使得当前代码上下文在正式执行之前就拥有了被声明的标识符。JavaScript 虽然被称为是动态语言,但拥有静态语义。

console.log(x); // undefined
var x = 100;
console.log(x); // 100
var y = "outer";
function f() {
console.log(y); // undefined
console.log(x); // throw a Exception
let x = 100;
var y = 100;
...
}

var y所声明的那个标识符在函数 f() 创建(它自己的闭包)时就已经存在,所以阻止了console.log(y)访问全局环境中的y。

let x所声明的那个x其实也已经存在 f() 函数的上下文环境中。访问它之所以会抛出异常(Exception),不是因为它不存在,而是因为这个标识符被拒绝访问。JavaScript 拒绝访问还没有绑定值的let/const标识符。

JavaScript 允许访问还没有绑定值的var所声明的标识符,称为变量声明(varDelcs),let/const 称为词法声明(lexicalDecls)。JavaScript 环境在创建一个变量名(varName in varDecls”后,会为它初始化绑定一个 undefined 值,而词法名字(lexicalNames)在创建之后不会,它们在缺省情况下就是还没有绑定值的标识符。

所有的声明本质上只有三种处理模式:var 变量声明、let 变量声明和 const 常量声明:

  • 函数按 varDecls 的规则声明;
  • 类的内部是处于严格模式中,名字按 let 处理;
  • import 导入的名字按 const 的规则处理。

赋值

  • 静态语言:在创建环境时将变量指向一个特定的初始值;
  • 动态语言:通过动态的执行过程来实现,即赋值操作。
lRef = rValue

将右操作数(的值)赋给左操作数(的引用):

LeftHandSideExpression < = | AssignmentOperator > AssignmentExpression

向一个不存在的变量赋值

早期的 JavaScript 中,如果你向一个不存在的变量名赋值,JavaScript 会在全局范围内创建它。在早期设计中,JavaScript 的全局环境是引擎使用一个称为全局对象的东西管理起来的。

JavaScript 引擎将全局的一些缺省对象、运行期环境的原生对象等东西都初始化在这个全局对象的属性中,并使用这个对象创建了一个称为全局对象闭包的东西,从而得到了 JavaScript 的全局环境。

当向一个不存在的变量赋值的时候,由于全局对象的属性表是可以动态添加的,因此 JavaScript 将变量名作为属性名添加给全局对象。而访问所谓全局变量时,就是访问这个全局对象的属性。实际效果就是可以动态地向全局环境中添加一个变量,可以删除掉这个动态添加的变量,因为本质上就是在删除全局对象的属性。

为了兼容旧的 JavaScript 语言设计,现在的 JavaScript 环境仍然通过将全局对象初始化为一个全局闭包来实现。但为了得到一个尽可能与其它变量环境相似的声明效果(varDecls),ECMAScript 规定在这个全局对象之外再维护一个变量名列表(varNames),所有在静态语法分析期或在 eval() 中使用var声明的变量名就被放在这个列表中。这个变量名列表中的变量是直接声明的变量,不能使用delete删除。

> var a = 100;
> x = 200;

# `a`和`x`都是global的属性
> Object.getOwnPropertyDescriptor(global, 'a');
{ value: 100, writable: true, enumerable: true, configurable: false }
> Object.getOwnPropertyDescriptor(global, 'x');
{ value: 200, writable: true, enumerable: true, configurable: true }

# `a`不能删除, `x`可以被删除
> delete a
false
> delete x
true

# 检查
> a
100
> x
ReferenceError: x is not defin
# 使用eval声明
> eval('var b = 300');

# 它的性质是可删除的
> Object.getOwnPropertyDescriptor(global, 'b').configurable;
true

# 检测与删除
> b
300
> delete b
true
> b
ReferenceError: b is not define

此时使用var声明的变量名尽管也会添加到 varNames 列表,但它也可以从 varNames 中移除(这是唯一一种能从 varNames 中移除项的特例,而 lexicalNames 中的项是不可移除的)。